package dsl.module.parser;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import dsl.common.domain.Action;
import dsl.common.domain.ActionType;
import dsl.common.domain.Event;
import dsl.common.domain.EventList;
import dsl.common.domain.Variable;
import dsl.common.domain.VariableList;
import dsl.common.exception.GrammarIsWrongException;
import dsl.utils.MyLogger;

/**
 * 语法分析器，将脚本分析成函数集{@link dsl.common.domain.EventList}events与变量集{@link dsl.common.domain.EventList}variables
 * 
 * 使用说明：先构造语法分析器对象，调用readProgram提取事件表与变量表存储在对象的events与variables
 * 然后可以通过get函数获得事件表与变量表
 */
public class Parser {
    /**
     * 返回值，表示文件未找到
     */
    public static final int FILE_NOT_FOUND = -1;
    /**
     * 返回值，表示发生IO错误
     */
    public static final int IO_ERROR = -2;
    /**
     * 返回值，表示成功完成语法分析
     */
    public static final int SUCCEED = 0;

    /**
     * 行数计数
     */
    private int line = 1;
    /**
     * 对于隐式申请的变量，用nameID转换为名称
     */
    private Integer nameId = 0;
    private EventList events = new EventList();
    private VariableList variables = new VariableList();

    /**
     * 分析用户编写的脚本程序，并生成中间代码
     * 
     * @param inputFilePath 脚本程序的路径
     * @return 返回值为int，代表不同的操作结果，详情见Parser的静态常量属性
     * @throws GrammarIsWrongException
     */
    public int readProgram(String inputFilePath) throws GrammarIsWrongException {
        // 初始化
        MyLogger.logger.info("分析开始");
        BufferedReader input = null;
        BufferedWriter output = null;
        File inputFile = new File(inputFilePath);
        File outputFile = new File("Intermediatecode/i-" + inputFile.getName());
        try {
            input = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), StandardCharsets.UTF_8));
            if (!outputFile.exists())
                outputFile.createNewFile();
            output = new BufferedWriter(new FileWriter(outputFile));

            // 读取脚本文件，分析程序
            analyzeProgram(input);
            input.close();

            // 将分析结果写入文件IntermedCode.txt
            output.write(variables.toString() + "\n");
            output.write(events.toString());

            output.close();
        } catch (FileNotFoundException e) {
            return FILE_NOT_FOUND;
        } catch (IOException e) {
            MyLogger.logger.warning("IO ERROR");
            return IO_ERROR;
        }
        return SUCCEED;
    }

    /**
     * @return EventList 事件表
     */
    public EventList getEvents() {
        return events;
    }

    /**
     * @return VariableList 结果表
     */
    public VariableList getVariables() {
        return variables;
    }

    /**
     * 分析脚本程序
     * 
     * @param input 脚本程序的读入流
     * @throws IOException
     * @throws GrammarIsWrongException
     */
    private void analyzeProgram(BufferedReader input) throws IOException, GrammarIsWrongException {
        // 读取程序，进行语法分析
        String str = "";
        int tag = 0;
        Event current = null;

        // 每读到一个标签，则进行相应的分析工作
        while ((str = readstr(input)) != null) {
            tag = confirmTag(str);
            // 除tag为1或-1时，其他操作都应该是event中的一个action，不满足条件报错
            if (current == null && (tag != 1 && tag != -1))
                throw new GrammarIsWrongException(line, "this statement not in event!");
            switch (tag) {
                // tag为1，表示开始标签为event，此时将产生一个event放入events中并且读取其名字
                // 主事件，即event.name=="main"时，放在events首位
                case 1:
                    current = new Event();
                    str = readstr(input);
                    if (str == null || confirmTag(str) != 3)
                        throw new GrammarIsWrongException(line, "event has no name!");
                    readName(input, current, ActionType.NAME);
                    break;
                // tag为2，表示开始标签为input，进行input的解析工作
                case 2:
                    readInput(input, current);
                    break;
                // tag为4，表示开始标签为output，进行output的解析工作
                case 4:
                    readStrings(input, current, ActionType.OUTPUT);
                    break;
                // tag为5，表示开始标签为case，进行case的解析工作
                // 其与Output的解析相似，都是读取一个字符串存入一个variable中，共用一个流程
                case 5:
                    readStrings(input, current, ActionType.CASE);
                    break;
                // tag为6，表示开始标签为then，进行then的解析工作
                // 其与name的解析相似，都是读取一个eventName中，共用一个流程
                case 6:
                    readName(input, current, ActionType.THEN);
                    break;
                // tag为7，表示开始标签为find，进行find的解析工作
                // 其与name的解析相似，都是读取一个name，共用一个流程
                case 7:
                    readName(input, current, ActionType.FIND);
                    break;
                // tag为8，表示开始标签为switch，进行switch的解析工作
                // 其与name的解析相似，读取一个variableName中，共用一个流程
                case 8:
                    readName(input, current, ActionType.SWITCH);
                    break;
                // tag为9，表示开始标签为default，无结束标签，加入default动作
                case 9:
                    current.actions.add(new Action(ActionType.DEFAULT.ordinal(), null));
                    break;
                // tag为-1，表示一个event的解析完成
                case -1:
                    if (current == null)
                        throw new GrammarIsWrongException(line, "error tag!");
                    else if (current.name.equals("main"))
                        events.add(0, current);
                    else
                        events.add(current);
                    current = null;
                    break;
                // 其它tag表示不应该在此出现的标签或错误标签
                default:
                    throw new GrammarIsWrongException(line, "error tag!");
            }
        }

        // 最后标签不是event的结束标签，抛出语法错误：未完成的程序
        if (tag != -1)
            throw new GrammarIsWrongException(line, "not end!");
        // 事件表中没有main事件，抛出语法错误：缺少主程序
        if (!events.get(0).name.equals("main"))
            throw new GrammarIsWrongException(line, "no main event!");
    }

    /**
     * 分析Input动作，将要求输入变量放入variables表，并将这个动作放入event事件
     * 
     * @param input 脚本程序的读入流
     * @param event 包含这个动作的事件
     * @throws IOException
     * @throws GrammarIsWrongException
     */
    private void readInput(BufferedReader input, Event event) throws IOException, GrammarIsWrongException {
        // 分析Input动作，将这个动作放入event事件，并将变量放入variables表
        String str = null;
        while ((str = readstr(input)) != null) {
            int tag = confirmTag(str);
            if (tag != 0) {
                if (tag == -2)
                    return;
                else
                    throw new GrammarIsWrongException(line, "error tag");
            }
            event.actions.add(new Action(ActionType.INPUT.ordinal(), str));
            Variable v = variables.findByName(str);
            if (v == null)
                variables.add(new Variable(str, null));
        }
        throw new GrammarIsWrongException(line, "error tag");
    }

    /**
     * 读取一个变量或事件的名字，并将相关操作写入事件表
     * 
     * @param input      脚本程序的读入流
     * @param event      包含这个动作的事件
     * @param actionType 与then共用一个分析流程，故需要标记动作类型
     * @throws IOException
     * @throws GrammarIsWrongException
     */
    private void readName(BufferedReader input, Event event, ActionType actionType)
            throws IOException, GrammarIsWrongException {
        // 读取一个事件或变量的名字
        String str = null;
        if ((str = readstr(input)) == null)
            throw new GrammarIsWrongException(line, "Missing parameter!");

        // 如果是name标签，即给事件命名，先检查事件表中是否已经有该事件
        // 没有则成功命名，有的话则抛出语法错误：已有该事件
        if (actionType == ActionType.NAME) {
            event.name = str;
            if (events.getIndexByName(str) != -1)
                throw new GrammarIsWrongException(line, str + " exists!");
        } else {
            // 其它标签，检查变量表或有要使用的变量
            // 有的话将操作添加到该事件的操作队列中，没有的话抛出语法错误：相关变量未查询到
            if (actionType != ActionType.THEN && variables.findByName(str) == null)
                throw new GrammarIsWrongException(line, str + " not found!");
            Action action = new Action(actionType.ordinal(), str);
            event.actions.add(action);
        }
        // 判断结束标签是否与开始标签一致，不一致抛出语法错误：错误的标签
        if ((str = readstr(input)) == null || confirmTag(str) != -actionType.ordinal())
            throw new GrammarIsWrongException(line, "error tag!");
    }

    /**
     * 读取一段字符串并将其写入变量表，将相应操作写入事件表
     * 
     * @param input      脚本程序的读入流
     * @param event      包含这个动作的事件
     * @param actionType 与then共用一个分析流程，故需要标记动作类型
     * @throws GrammarIsWrongException
     * @throws IOException
     */
    private void readStrings(BufferedReader input, Event event, ActionType actionType)
            throws GrammarIsWrongException, IOException {
        // 将一段字符串读入，用变量存储起来
        input.readLine();
        String str = input.readLine();
        line = line + 2;
        str = str.trim();
        // 读取第一段字符串，第一段如果就是标签，抛出语法错误：缺少文本
        int tag = confirmTag(str);
        if (tag != 0)
            throw new GrammarIsWrongException(line - 1, "Content missing!");
        while (true) {
            String current = input.readLine();
            line++;
            current = current.trim();
            tag = confirmTag(current);

            // tag为零，表示不是标签，继续读取
            if (tag == 0)
                str = str + "\n" + current;
            // tag为结束标签，结束阅读
            else if (tag == -actionType.ordinal()) {
                variables.add(new Variable(nameId.toString(), str));
                event.actions.add(new Action(actionType.ordinal(), nameId.toString()));
                nameId++;
                return;
            } else
                // tag为错误的标签，抛出语法错误：错误的标签
                throw new GrammarIsWrongException(line, "error tag");
        }
    }

    /**
     * 读取一个字符串
     * 
     * @param input 脚本程序的读入流
     * @return String 一个字符串（以asc码小于32位的字符作为分割符）
     * @throws IOException
     */
    private String readstr(BufferedReader input) throws IOException {
        // 读取一个字符串，并返回。如果文件到结尾，返回null。
        String str = "";
        int current = -1;
        while ((current = input.read()) != -1) {
            char a = (char) (current);
            if (a == '\n')
                line++;
            if (a != ' ' && a >= 32) {
                str = str + a;
                break;
            }
        }
        while ((current = input.read()) != -1) {
            char a = (char) (current);
            if (a == '\n')
                line++;
            if (a == ' ' || a <= 32)
                return str;
            else
                str = str + a;
        }
        if (str.equals(""))
            str = null;
        return str;
    }

    /**
     * 分析这个标签是什么标签
     *
     * @param str 标签
     * @return int 其绝对值表示标签的类型（见ActionType），正值表示开始标签，负值表示结束标签
     */
    private static int confirmTag(String str) {
        int key = 1, value = 0, begin = 1;
        if (str.charAt(0) != '<')
            return 0;
        if (str.charAt(str.length() - 1) != '>')
            return 0;
        if (str.charAt(1) == '/') {
            key = -1;
            begin = 2;
        }
        String tag = str.substring(begin, str.length() - 1);
        tag = tag.toUpperCase();
        try {
            ActionType actionType = ActionType.valueOf(tag);
            value = actionType.ordinal();
        } catch (IllegalArgumentException e) {
            value = 0;
        }
        return key * value;
    }
}